/**
 * supervisor module
 * 
 * @author strlst <e11907086@student.tuwien.ac.at>
 * @date 2020-11-06
 * @brief contains methods for setup of shared memory and semaphores, which are utilized to read data from a shared memory circular_buffer
 * @details uses a global variable exit_requested for signal handlign
 */

#include "supervisor.h"

volatile sig_atomic_t exit_requested = 0;

/**
 * @brief takes a struct circular_buffer and count value to print result in the form of a list of edges
 * @details assumes count >= 2
 * @param buffer circular_buffer containing result values
 * @param count integer representing total edge indices in buffer
 */
static inline void print_result_from_buffer(struct circular_buffer *buffer, int count) {
    /* don't bother */
    if (count < 2)
        return;
    /* print all edges that need to be removed */
    printf("by removing %i edge%s: ", count / 2, count / 2 == 1 ? "" : "s");
    for (int i = 0; i < (count - 2); i += 2) {
        printf(
            "%d-%d, ",
            buffer->edges[buffer->out][i],
            buffer->edges[buffer->out][i + 1]
        );
    }
    /* print final edge differently */
    printf(
        "%d-%d\n",
        buffer->edges[buffer->out][count - 2],
        buffer->edges[buffer->out][count - 1]
    );
}

void collect_results(struct circular_buffer *buffer, sem_t *s_access, sem_t *s_used, sem_t *s_free) {
    /* initiate buffer and start querying solutions */
    buffer->quit = 0;
    buffer->in   = 0;
    buffer->out  = 0;
    /* memorize best solution */
    int best = INT_MAX;
    do {
        /* flush in case */
        fflush(stdout);

        /* prevent unsynchronized IO */
        if (sem_wait(s_used) == -1 && !exit_requested)
            printf("sem_wait(s_used) error\n");
        if (sem_wait(s_access) == -1 && !exit_requested)
            printf("sem_wait(s_access) error\n");

        //printf("enter solution, buffer->out: %li\n", buffer->out);

        int e = 0, total = 0;
        unsigned int edge1, edge2;
        /* parse edges until delimiter edge appears */
        /* loop edges appearing ought to be impossible at this stage, */
        /* check nonetheless */
        while (((edge1 = buffer->edges[buffer->out][e])     != DELIMITER_EDGE) &&
               ((edge2 = buffer->edges[buffer->out][e + 1]) != DELIMITER_EDGE)) {
            /* increment loop variable and tally total */
            e += 2;
            total += 2;
        }

        /* print if solution is the best solution yet */
        if (total < best) {
            /* set new global best */
            best = total;
            /* if best is 0, inform user that no edges need to be removed */
            if (best == 0)
                printf("graph is already 3colorable\n");
            else
                print_result_from_buffer(buffer, total);
        }

        /* increment solution read index */
        buffer->out = (buffer->out + 1) % MAX_SOLUTIONS;

        //printf("finish solution, buffer->out: %li\n", buffer->out);

        /* finish synchronized IO */
        if (sem_post(s_access) == -1 && !exit_requested)
            printf("sem_post(s_access) error\n");
        if (sem_post(s_free) == -1 && !exit_requested)
            printf("sem_post(s_free) error\n");
    /* query until exit is requested via signals */
    } while (!exit_requested);

    /* signal end to generators */
    buffer->quit = 1;
}

int supervise_init(struct circular_buffer *buffer) {
    /* create semaphores */
    /* access as mutex, used and free as counters */
    sem_t *s_access = sem_open(
        SEM_ACCESS, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 1
    );
    sem_t *s_used = sem_open(
        SEM_USED, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 0
    );
    sem_t *s_free = sem_open(
        SEM_FREE, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, MAX_SOLUTIONS
    );
    /* verify semaphores were created successfully */
    if (s_access   == SEM_FAILED ||
        s_used     == SEM_FAILED ||
        s_free     == SEM_FAILED)
        return OS_SEM_OPEN_FAILED;

    /* collect results from generators */
    collect_results(buffer, s_access, s_used, s_free);

    /* close semaphores and check for errors */
    if (sem_close(s_access)   == -1 ||
        sem_close(s_used)     == -1 ||
        sem_close(s_free)     == -1)
        return OS_SEM_CLOSE_FAILED;

    /* unlink semaphores and check for errors */
    if (sem_unlink(SEM_ACCESS)   == -1 ||
        sem_unlink(SEM_USED)     == -1 ||
        sem_unlink(SEM_FREE)     == -1)
        return OS_SEM_UNLINK_FAILED;

    return OS_SUCCESS;
}

int supervise() {
    /* create/open shared memory object */
    int shmfd = shm_open(SHM_NAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    /* die on error */
    if (shmfd == -1)
        return OS_SHM_OPEN_FAILED;

    if (ftruncate(shmfd, MAX_DATA * MAX_SOLUTIONS * 4) == -1)
        return OS_FTRUNCATE_FAILED;

    /* map shared memory object */
    struct circular_buffer *buffer = mmap(
        NULL,
        sizeof(*buffer),
        PROT_READ | PROT_WRITE,
        MAP_SHARED,
        shmfd,
        0
    );

    /* verify that map was successful */
    if (buffer == MAP_FAILED)
        return OS_MAP_FAILED;

    /* set up signal handlers for exit */
    struct sigaction sa = { .sa_handler = &handle_signal };
    sigaction(SIGINT,  &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

    int ret = supervise_init(buffer);
    if (ret != OS_SUCCESS)
        return ret;

    /* close shared memory file descriptor */
    if (close(shmfd) == -1)
        return OS_SHM_CLOSE_FAILED;

    /* unmap shared memory object */
    if (munmap(buffer, sizeof(*buffer)) == -1)
        return OS_UNMAP_FAILED;

    if (shm_unlink(SHM_NAME) == -1)
        return OS_SHM_UNLINK_FAILED;

    return OS_SUCCESS;
}

void handle_signal(int signal) {
    exit_requested = 1;
}
